/*
 * Rect2D.h
 *
 *  Created on: 2009/01/23
 *      Author: Yongshik Cho
 */

#ifndef RECT2D_H_
#define RECT2D_H_

//#include <cmath>

#ifndef TRIANGLE2D_H_
#	include "Triangle2D.h"
#endif
#include "sqplus/sqplus.h"
using namespace SqPlus;


class Rect2D : public Collidable
{
public:
	enum CenterType
	{
		CENTER_NONE = 0,
		CENTER_LT,
		CENTER_CT,
		CENTER_RT,
		CENTER_LM,
		CENTER_CM,
		CENTER_RM,
		CENTER_LB,
		CENTER_CB,
		CENTER_RB
	};

public:
	Rect2D(uint32 type = Collidable::COLLIDABLE_RECT) :
		Collidable(type)
	{
		mPoints.clear();
		mPoints.push_back(Vector2D(0.f, 0.f));
		mPoints.push_back(Vector2D(0.f, 0.f));
		mPoints.push_back(Vector2D(0.f, 0.f));
		mPoints.push_back(Vector2D(0.f, 0.f));

		mClosed = true;
		mCenterType = CENTER_CM;
	}
	Rect2D(const float width , const float height, uint32 type = Collidable::COLLIDABLE_RECT) :
		Collidable(type)
	{
		mWidth = width;
		mHeight = height;

		mPoints.clear();
		mPoints.push_back(Vector2D(0.f, 0.f));
		mPoints.push_back(Vector2D(mWidth, 0.f));
		mPoints.push_back(Vector2D(mWidth, mHeight));
		mPoints.push_back(Vector2D(0.f, mHeight));

		mClosed = true;
		mCenterType = CENTER_CM;
	}
	Rect2D(const Vector2D& p1, const Vector2D& size, uint32 type = Collidable::COLLIDABLE_RECT) :
		Collidable(type)
	{
		mWidth = size.x;
		mHeight = size.y;

		mPoints.clear();
		mPoints.push_back(Vector2D(0.f, 0.f));
		mPoints.push_back(Vector2D(mWidth, 0.f));
		mPoints.push_back(Vector2D(mWidth, mHeight));
		mPoints.push_back(Vector2D(0.f, mHeight));

		for(vector<Vector2D>::iterator it=mPoints.begin();it!=mPoints.end();++it)
			*it += p1;

		mClosed = true;
		mCenterType = CENTER_CM;
	}

	Rect2D(const Vector2D& p1, const Vector2D& p2, const Vector2D& p3, const Vector2D& p4, uint32 type = Collidable::COLLIDABLE_RECT) :
		Collidable(type)
	{
		mPoints.clear();
		mPoints.push_back(p1);
		mPoints.push_back(p2);
		mPoints.push_back(p3);
		mPoints.push_back(p4);
		mCenterType = CENTER_CM;
	}

	Rect2D(const Rect2D& rhs, uint32 type = Collidable::COLLIDABLE_RECT) :
		Collidable(type)
	{
		mWidth = rhs.mWidth;
		mHeight = rhs.mHeight;
		mCenterType = rhs.mCenterType;

		mPoints.clear();
		mPoints.push_back(rhs.mPoints[0]);
		mPoints.push_back(rhs.mPoints[1]);
		mPoints.push_back(rhs.mPoints[2]);
		mPoints.push_back(rhs.mPoints[3]);
	}
	virtual ~Rect2D() {}

public:
	inline float getWidth() const { return mWidth; }
	inline float getHeight() const { return mHeight; }

	inline const Rect2D getAxisAlinedRect()
	{
		float minx1 = min(mPoints[0].x, mPoints[1].x);
		float minx2 = min(mPoints[2].x, mPoints[3].x);
		float minx = min(minx1, minx2);

		float maxx1 = max(mPoints[0].x, mPoints[1].x);
		float maxx2 = max(mPoints[2].x, mPoints[3].x);
		float maxx = max(maxx1, maxx2);

		float miny1 = min(mPoints[0].y, mPoints[1].y);
		float miny2 = min(mPoints[2].y, mPoints[3].y);
		float miny = min(miny1, miny2);

		float maxy1 = max(mPoints[0].y, mPoints[1].y);
		float maxy2 = max(mPoints[2].y, mPoints[3].y);
		float maxy = max(maxy1, maxy2);

		return Rect2D(maxx - minx, maxy - miny);
	}

	inline const bool in(const Vector2D& p)
	{
		if(Triangle2D(mPoints[0], mPoints[1], mPoints[2]).in(p))
			return true;

		if(Triangle2D(mPoints[0], mPoints[2], mPoints[3]).in(p))
			return true;

		return false;
	}
	inline const bool in(const Line2D& line)
	{
		if(in(line.mPoints[0]) && in(line.mPoints[1]))
			return true;

		return false;
	}
	inline const bool in(const Triangle2D& tris)
	{
		if(in(tris.mPoints[0]) && in(tris.mPoints[1]) && in(tris.mPoints[2]))
			return true;

		return false;
	}
	inline const bool in(const Rect2D& rect)
	{
		if(in(rect.mPoints[0]) && in(rect.mPoints[1]) && in(rect.mPoints[2]) && in(rect.mPoints[3]))
			return true;

		return false;
	}

	inline const bool intersects(const Line2D& line)
	{
		if(Triangle2D(mPoints[0], mPoints[1], mPoints[2]).intersects(line))
			return true;

		if(Triangle2D(mPoints[0], mPoints[2], mPoints[3]).intersects(line))
			return true;

		return false;
	}
	inline const bool isPointIn(const Rect2D& rect)
	{
		if(in(rect.mPoints[0]) || in(rect.mPoints[1]) || in(rect.mPoints[2]) || in(rect.mPoints[3]))
			return true;

		return false;
	}

	const bool intersects(Triangle2D& rhs)
	{
		Triangle2D t1(rhs.mPoints[0], rhs.mPoints[1], rhs.mPoints[2]);
		if(t1.intersects(rhs))
			return true;

		Triangle2D t2(rhs.mPoints[0], rhs.mPoints[2], rhs.mPoints[3]);
		if(t2.intersects(rhs))
			return true;
	}

	const bool intersects(Rect2D& rhs)
	{
		Triangle2D t1(rhs.mPoints[0], rhs.mPoints[1], rhs.mPoints[2]);

		Line2D* line = 0;

		rhs.setLineIndex(0);
		line = (Line2D*)rhs.getNextLine();
		while(line)
		{
			if(t1.intersects(*line))
				return true;

			line = (Line2D*)rhs.getNextLine();
		}

		Triangle2D t2(rhs.mPoints[0], rhs.mPoints[2], rhs.mPoints[3]);

		rhs.setLineIndex(0);
		line = (Line2D*)rhs.getNextLine();
		while(line)
		{
			if(t2.intersects(*line))
				return true;

			line = (Line2D*)rhs.getNextLine();
		}

		return false;
	}

	inline Vector2D getRuledRectPoint(const uint8 centerType)
	{
		switch(centerType)
		{
		case Rect2D::CENTER_LT:
			return Vector2D(0.f, 0.f);
		case Rect2D::CENTER_CT:
			return Vector2D(mWidth/2.f, 0.f);
		case Rect2D::CENTER_RT:
			return Vector2D(mWidth, 0.f);
		case Rect2D::CENTER_LM:
			return Vector2D(0.f, mHeight/2.f);
		case Rect2D::CENTER_CM:
			return Vector2D(mWidth/2.f, mHeight/2.f);
		case Rect2D::CENTER_RM:
			return Vector2D(mWidth, mHeight/2.f);
		case Rect2D::CENTER_LB:
			return Vector2D(0.f, mHeight);
		case Rect2D::CENTER_CB:
			return Vector2D(mWidth/2.f, mHeight);
		case Rect2D::CENTER_RB:
			return Vector2D(mWidth, mHeight);
		}

		return Vector2D(0.f, 0.f);
	}

	inline Vector2D getLocalPoint(const uint8 centerType)
	{
		switch(centerType)
		{
		case Rect2D::CENTER_LT:
			return getLocalPosition(mPoints[0]);
		case Rect2D::CENTER_RT:
			return getLocalPosition(mPoints[1]);
		case Rect2D::CENTER_RB:
			return getLocalPosition(mPoints[2]);
		case Rect2D::CENTER_LB:
			return getLocalPosition(mPoints[3]);
		}

		return Vector2D(0.f, 0.f);
	}

	inline Vector2D getPoint(const uint8 centerType)
	{
		switch(centerType)
		{
		case Rect2D::CENTER_LT:
			return mPoints[0];
		case Rect2D::CENTER_CT:
			return (mPoints[0] + mPoints[1]) / 2.f;
		case Rect2D::CENTER_RT:
			return mPoints[1];
		case Rect2D::CENTER_LM:
			return (mPoints[0] + mPoints[3]) / 2.f;
		case Rect2D::CENTER_CM:
			return (mPoints[0] + mPoints[2]) / 2.f;
		case Rect2D::CENTER_RM:
			return (mPoints[1] + mPoints[2]) / 2.f;
		case Rect2D::CENTER_LB:
			return mPoints[3];
		case Rect2D::CENTER_CB:
			return (mPoints[2] + mPoints[3]) / 2.f;
		case Rect2D::CENTER_RB:
			return mPoints[2];
		}

		return Vector2D(0.f, 0.f);
	}

	inline const bool operator == (const Rect2D& rect) const
	{
		return (mPoints[0] == rect.mPoints[0] && mPoints[1] == rect.mPoints[1] &&
			mPoints[2] == rect.mPoints[2] && mPoints[3] == rect.mPoints[3]);
	}

	/** Function for writing to a stream.
	*/
	inline friend std::ostream& operator <<
		( std::ostream& o, const Rect2D& r )
	{
		o << "Rect2D(" << r.mPoints[0] << ", " << r.mPoints[1] << ", "
			<< r.mPoints[2] << ", " << r.mPoints[3] <<  ")";

		return o;
	}

private:
	float 		mWidth;
	float 		mHeight;

};

//must be declared here
DECLARE_INSTANCE_TYPE(Rect2D)

#endif /* RECT2D_H_ */
